using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using Uniject;
using UnityEngine;
using UnityEngine.Purchasing;
using UnityEngine.Purchasing.Default;
using UnityEngine.Purchasing.Extension;

namespace UnityEngine.Purchasing
{
    /// <summary>
    /// Handles Windows 8.1.
    /// </summary>
    internal class WinRTStore : AbstractStore, IWindowsIAPCallback, IMicrosoftExtensions
    {
        private IWindowsIAP win8;
        private IStoreCallback callback;
        private readonly IUtil util;
        private readonly ILogger logger;

        private bool m_CanReceivePurchases = false;

        public WinRTStore(IWindowsIAP win8, IUtil util, ILogger logger)
        {
            this.win8 = win8;
            this.util = util;
            this.logger = logger;
        }

        /// <summary>
        /// Allow the windows IAP service to be swapped
        /// out since the Application developer can switch
        /// between sandbox/live after this store is
        /// constructed.
        /// </summary>
        public void SetWindowsIAP(IWindowsIAP iap)
        {
            win8 = iap;
        }

        public override void Initialize(IStoreCallback biller)
        {
            callback = biller;
        }

        public override void RetrieveProducts(ReadOnlyCollection<ProductDefinition> productDefs)
        {
            var dummyProducts = from def in productDefs
                                where def.type != ProductType.Subscription
                                select new WinProductDescription(
                                    def.storeSpecificId, "$0.01",
                                    "Fake title - " + def.storeSpecificId,
                                    "Fake description - " + def.storeSpecificId,
                                    "USD", 0.01m, null, null, def.type == ProductType.Consumable);
            win8.BuildDummyProducts(dummyProducts.ToList());
            init(0);
        }

        public override void FinishTransaction(ProductDefinition product, string transactionId)
        {
            win8.FinaliseTransaction(transactionId);
        }

        private void init(int delay)
        {
            win8.Initialize(this);
            win8.RetrieveProducts(true);
        }

        public override void Purchase(ProductDefinition product, string developerPayload)
        {
            win8.Purchase(product.storeSpecificId);
        }

        // An Action<bool> invoked on pause/resume.
        public void restoreTransactions(bool pausing)
        {
            if (!pausing)
            {
                if (m_CanReceivePurchases)
                {
                    win8.RetrieveProducts(false);
                }
            }
        }

        public void RestoreTransactions()
        {
            win8.RetrieveProducts(false);
            // setting this here assumes that the Retrieve actually worked, but in the
            // case where it didn't we still want to persist that the user has tried to restore
            // to see if the automatic attempts (on app FG) resolve things
            m_CanReceivePurchases = true;
        }

        public void logError(string error)
        {
            // Uncomment to get diagnostics printed on screen.
            logger.LogError("Unity Purchasing", error);
        }

        public void OnProductListReceived(WinProductDescription[] winProducts)
        {
            util.RunOnMainThread(() =>
            {
                // Convert windows products to Unity Purchasing products.
                var products = from product in winProducts
                               let metadata = new ProductMetadata(
                                   product.price, product.title, product.description,
                                   product.ISOCurrencyCode, product.priceDecimal)
                               select new ProductDescription(
                                   product.platformSpecificID,
                                   metadata,
                                   product.receipt,
                                   product.transactionID);
                // need to determine if that list includes any purchases or just products
                // and then use that to set m_CanReceivePurchases
                callback.OnProductsRetrieved(products.ToList());
            });
        }

        public void log(string message)
        {
            util.RunOnMainThread(() =>
            {
                logger.Log(message);
            });
        }

        public void OnPurchaseFailed(string productId, string error)
        {
            util.RunOnMainThread(() =>
            {
                logger.LogFormat(LogType.Error, "Purchase failed: {0}, {1}", productId, error);
                if ("AlreadyPurchased" == error)
                {
                    try
                    {
                        callback.OnPurchaseFailed(new PurchaseFailureDescription(productId,
                            (PurchaseFailureReason)Enum.Parse(typeof(PurchaseFailureReason), "DuplicateTransaction"), error));
                    }
                    catch
                    {
                        callback.OnPurchaseFailed(new PurchaseFailureDescription(productId,
                            (PurchaseFailureReason)Enum.Parse(typeof(PurchaseFailureReason), "Unknown"), error));
                    }
                }
                else if ("NotPurchased" == error)
                {
                    callback.OnPurchaseFailed(new PurchaseFailureDescription(productId,
                        (PurchaseFailureReason)Enum.Parse(typeof(PurchaseFailureReason), "UserCancelled"), error));
                }
                else
                {
                    callback.OnPurchaseFailed(new PurchaseFailureDescription(productId,
                        (PurchaseFailureReason)Enum.Parse(typeof(PurchaseFailureReason), "Unknown"), error));
                }
            });
        }

        public void OnPurchaseSucceeded(string productId, string receipt, string tranId)
        {
            util.RunOnMainThread(() =>
            {
                m_CanReceivePurchases = true;
                callback.OnPurchaseSucceeded(productId, receipt, tranId);
            });
        }

        // When using an incorrect product id:
        // "Exception from HRESULT: 0x805A0194"
        public void OnProductListError(string message)
        {
            util.RunOnMainThread(() =>
            {
                if (message.Contains("801900CC"))
                {
                    callback.OnSetupFailed(InitializationFailureReason.AppNotKnown, null);
                }
                else
                {
                    logError("Unable to retrieve product listings. UnityIAP will automatically retry...");
                    logError(message);
                    init(3000);
                }
            });
        }
    }
}
